#include <iostream>
#include <vector>
#include <queue>
#include <bitset>
#include <cstring> // For memset
#include <algorithm> // For std::min

// 定义常量
const int MAXN_NODES = 2007; // 最大节点数 + 缓冲区
const int MAX_EDGES = 3007; // 最大边数 + 缓冲区
const long long INF = 0x3f3f3f3f3f3f3f3fLL; // long long 类型的无穷大

// 边结构体
struct Edge {
    int to;                 // 边的终点
    int weight;             // 边的权重
    int next_edge_idx;      // 邻接表中下一条边的索引
};

Edge edges[MAX_EDGES];              // 存储所有边
int head[MAXN_NODES];               // 邻接表头数组
int edge_count;                     // 当前边的数量

long long dist_from_s[MAXN_NODES];      // 从当前源点s到各点的最短距离 (Dijkstra中用)
bool visited[MAXN_NODES];               // Dijkstra中标记节点是否已访问
long long true_dist[MAXN_NODES][MAXN_NODES]; // true_dist[i][j] 存储i到j的真实最短路
long long initial_weights[MAXN_NODES][MAXN_NODES]; // initial_weights[i][j] 存储i到j的直接边权

// F[u][v]为true表示dist'(u,v) == dist(u,v)
std::bitset<MAXN_NODES> F[MAXN_NODES];
// G[v][u]为true表示dist'(u,v) == dist(u,v) (F的转置)
std::bitset<MAXN_NODES> G[MAXN_NODES];
// Dijkstra从s出发, intermediate_nodes_on_sp[v]存储了s到v某条最短路径上的所有中间节点(包括s)
std::bitset<MAXN_NODES> intermediate_nodes_on_sp[MAXN_NODES];

int N_nodes, M_edges_count; // 图的节点数和边数

// 添加边到邻接表
void add_edge(int u, int v, int w) {
    edge_count++;
    edges[edge_count].to = v;
    edges[edge_count].weight = w;
    edges[edge_count].next_edge_idx = head[u];
    head[u] = edge_count;
}

// 从源点s运行Dijkstra算法
// 计算dist_from_s[] 和 intermediate_nodes_on_sp[]
void run_dijkstra(int s) {
    // 初始化
    for (int i = 1; i <= N_nodes; ++i) {
        dist_from_s[i] = INF;
        visited[i] = false;
        intermediate_nodes_on_sp[i].reset(); // 重置路径节点bitset
    }

    dist_from_s[s] = 0;
    // 优先队列存储 {-distance, node_id} 实现最小堆效果
    std::priority_queue<std::pair<long long, int>> pq;
    pq.push({0, s});

    while (!pq.empty()) {
        int u = pq.top().second;
        long long u_d = -pq.top().first; // 取出真实距离
        pq.pop();

        // 如果节点u已经访问过，或者找到了更短的路径（针对非严格优先队列），则跳过
        if (visited[u] && u_d > dist_from_s[u]) { // u_d > dist_from_s[u] handles non-strict extract for multiple path with same length
             continue;
        }
        if(visited[u]) continue; // If strictly using visited flag
        
        visited[u] = true;

        // 遍历u的所有出边
        for (int i = head[u]; i != 0; i = edges[i].next_edge_idx) {
            int v = edges[i].to;
            int w = edges[i].weight;

            if (dist_from_s[v] > dist_from_s[u] + w) { // 找到更短路径
                dist_from_s[v] = dist_from_s[u] + w;
                pq.push({-dist_from_s[v], v});
                // 继承路径节点，并加入u
                intermediate_nodes_on_sp[v] = intermediate_nodes_on_sp[u];
                intermediate_nodes_on_sp[v][u] = 1;
            } else if (dist_from_s[v] == dist_from_s[u] + w) { // 找到等长最短路径
                // 合并路径节点，并加入u
                intermediate_nodes_on_sp[v] |= intermediate_nodes_on_sp[u];
                intermediate_nodes_on_sp[v][u] = 1;
            }
        }
    }
}

int main() {
    // 使用标准cin/cout
    std::ios_base::sync_with_stdio(false); // 仍然保留，因为这是竞赛常见做法，若严格要求则注释掉
    std::cin.tie(NULL);                   // 同上

    std::cin >> N_nodes >> M_edges_count;

    // 初始化邻接表和初始权重矩阵
    edge_count = 0;
    for (int i = 1; i <= N_nodes; ++i) {
        head[i] = 0; // 邻接表尾标记
        for (int j = 1; j <= N_nodes; ++j) {
            if (i == j) {
                initial_weights[i][j] = 0;
            } else {
                initial_weights[i][j] = INF;
            }
        }
    }

    // 读入边，构建图
    for (int i = 0; i < M_edges_count; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        add_edge(u, v, w);
        // 简单图可能有多条u->v的平行边在输入中，但题目描述是简单图，这里min是为了处理万一不是严格简单图输入
        initial_weights[u][v] = std::min(initial_weights[u][v], (long long)w);
    }

    // 1. 计算所有点对的真实最短路径 true_dist[i][j]
    for (int i = 1; i <= N_nodes; ++i) {
        run_dijkstra(i); // Dijkstra计算从i出发的最短路及路径信息
        for (int j = 1; j <= N_nodes; ++j) {
            true_dist[i][j] = dist_from_s[j]; // 保存最短路长度
        }
    }

    // 2. 初始化 F 和 G 的基本情况
    //    如果 u==v 或者 (u,v) 的最短路是其直达边，则该点对可被正确计算
    for (int u = 1; u <= N_nodes; ++u) {
        for (int v = 1; v <= N_nodes; ++v) {
            if (u == v || true_dist[u][v] == initial_weights[u][v]) {
                F[u][v] = 1;
                G[v][u] = 1; // G[v][u] 存储 F[u][v]
            }
        }
    }

    long long count_incorrect_pairs = 0;

    // 3. 通过递归定义判断其他点对是否能被正确计算
    for (int u = 1; u <= N_nodes; ++u) { // 枚举源点u
        // 再次运行Dijkstra以获取从u出发到各点的最短路径上的中间节点信息
        run_dijkstra(u);
        //此时 intermediate_nodes_on_sp[v][k]=1 表示 k 在 u到v 的某条最短路径上

        for (int v = 1; v <= N_nodes; ++v) { // 枚举终点v
            if (!F[u][v]) { // 如果(u,v)不满足基本情况
                // 检查是否存在中间点k，使得 dist(u,v)=dist(u,k)+dist(k,v)
                // 并且 (u,k) 和 (k,v) 都能被正确计算
                // F[u] 是 F 矩阵的第u行
                // G[v] 是 G 矩阵的第v行 (即 F 矩阵的第v列)
                // intermediate_nodes_on_sp[v] 是从u到v的最短路径上的节点集合
                // 按位与后若非空，则表示存在这样的k
                if (!(F[u] & G[v] & intermediate_nodes_on_sp[v]).none()) {
                    F[u][v] = 1; // (u,v)可以被正确计算
                    G[v][u] = 1;
                } else {
                    count_incorrect_pairs++; // (u,v)不能被正确计算
                }
            }
        }
    }

    std::cout << count_incorrect_pairs << std::endl;

    return 0;
}